package com.hospital.wx.service.Impl;

import com.hospital.wx.dao.PatientVisitsDao;
import com.hospital.wx.pojo.PatientVisits;
import com.hospital.wx.service.PatientVisitsService;
import com.hospital.wx.util.MinioUtil;
import com.hospital.wx.util.WxMaUtil;
import com.itextpdf.text.*;
import com.itextpdf.text.Document;
import com.itextpdf.text.Image;
import com.itextpdf.text.Rectangle;
import lombok.extern.slf4j.Slf4j;
import com.itextpdf.text.pdf.BaseFont;
import com.itextpdf.text.pdf.PdfPCell;
import com.itextpdf.text.pdf.PdfPTable;
import com.itextpdf.text.pdf.PdfWriter;
import com.itextpdf.text.pdf.draw.LineSeparator;
import io.minio.BucketExistsArgs;
import io.minio.MakeBucketArgs;
import io.minio.MinioClient;
import io.minio.PutObjectArgs;
import com.itextpdf.text.Font;
import org.apache.poi.xwpf.usermodel.*;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.http.*;
import org.springframework.stereotype.Service;
import org.springframework.web.client.RestTemplate;

import javax.imageio.ImageIO;
import java.awt.*;
import java.awt.image.BufferedImage;
import java.io.*;
import java.net.URL;
import java.text.SimpleDateFormat;
import java.time.ZoneId;
import java.time.ZonedDateTime;
import java.time.format.DateTimeFormatter;
import java.util.*;
import java.util.concurrent.TimeUnit;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

@Service
@Slf4j
public class PatientVisitsServiceImpl implements PatientVisitsService {

    @Autowired
    private PatientVisitsDao patientVisitsDao;

    @Value("${minio.bucket-name}")
    private String shareBucket;
    @Value("${hospital.name}")
    private String hospitalName;

    @Value("${hospital.logo.path}")
    private String logoPath;

    @Autowired
    private MinioClient minioClient;

    @Autowired
    private WxMaUtil wxMaUtil;

    @Autowired
    private RestTemplate restTemplate;

    @Autowired
    private MinioUtil minioUtil;

    @Autowired
    private WxAccessTokenService wxAccessTokenService;

    @Autowired
    private RedisTemplate<String, String> redisTemplate;

    private static final int WIDTH = 800;
    private static final int HEIGHT = 1200;
    private static final String FONT_NAME = "Microsoft YaHei";

    private static final String QRCODE_CACHE_KEY = "qrcode:";   //存储在redis的二维码Base64数据的Key

    private static final long QRCODE_CACHE_TIME = 24 * 60 * 60; // 24小时，存储在redis的时间

    @Override
    public ArrayList<HashMap> selectAll() {
        return patientVisitsDao.selectAll();
    }

    /*
    * *********************生成PDF进行导出就诊记录*****************************
    * */
    @Override
    public byte[] generateVisitReport(Integer visitId, String exportType) throws Exception{

        // 1. 获取就诊记录详情
        PatientVisits visit = patientVisitsDao.selectByPatientId(visitId);
        if (visit == null) {
            throw new BusinessException("就诊记录不存在", new Exception());
        }

        // 2. 根据导出类型选择不同的模板
        String templatePath = getTemplatePath(exportType);

        // 3. 使用模板引擎生成文档
        Map<String, Object> data = new HashMap<>();
        data.put("visit", visit);
        data.put("hospital", hospitalName);
        data.put("currentTime", new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(new Date()));

        if ("pdf".equals(exportType)) {
            return generatePDF(templatePath, data);
        } else {
            return generateWord(templatePath, data);
        }
    }


    // 假设的异常类
    class BusinessException extends RuntimeException {
        public BusinessException(String message, Throwable cause) {
            super(message, cause);
        }
    }

    private String getTemplatePath(String exportType) {
        if ("pdf".equals(exportType)) {
            return "pdf";
        } else {
            return "docx";
        }
    }

//    private static final Logger log = LoggerFactory.getLogger(PatientVisitsServiceImpl.class);

    /*
    * 加载使用字体
    * */
    private BaseFont loadFont() {
        try {
            // 使用字节流加载字体
            InputStream fontStream = this.getClass().getClassLoader().getResourceAsStream("fonts/simsun.ttc");
            if (fontStream == null) {
                throw new IOException("无法找到字体文件");
            }

            // 将字体文件读入字节数组
            ByteArrayOutputStream buffer = new ByteArrayOutputStream();
            int nRead;
            byte[] data = new byte[1024];
            while ((nRead = fontStream.read(data, 0, data.length)) != -1) {
                buffer.write(data, 0, nRead);
            }
            buffer.flush();
            byte[] fontBytes = buffer.toByteArray();

            // 使用字节数组创建字体
            //在 createFont 方法中添加 ,0 后缀，因为 .ttc 是字体集合文件，需要指定使用第一个字体
            return BaseFont.createFont("simsun.ttc,0", BaseFont.IDENTITY_H, BaseFont.EMBEDDED, true, fontBytes, null);
        } catch (Exception e) {
            log.error("加载内置字体失败", e);
            throw new BusinessException("PDF生成失败：无法加载字体文件", e);
        }
    }

    /*
    * 生成PDF文档的方法
    * */
    private byte[] generatePDF(String templatePath, Map<String, Object> data) {
        try {
            Document document = new Document();
            ByteArrayOutputStream baos = new ByteArrayOutputStream();
            PdfWriter.getInstance(document, baos);
            document.open();

            // 添加医院 Logo
            Image logo = Image.getInstance(logoPath);
            document.add(logo);

            // 加载字体
            BaseFont baseFont = loadFont();

            // 创建不同大小的字体
            Font titleFont = new Font(baseFont, 18, Font.BOLD);
            Font subtitleFont = new Font(baseFont, 14, Font.BOLD);
            Font contentFont = new Font(baseFont, 12, Font.NORMAL);

            // 添加标题
            Paragraph title = new Paragraph("就诊记录", titleFont);
            title.setAlignment(Element.ALIGN_CENTER);
            title.setSpacingAfter(20);
            document.add(title);

            // 添加基本信息
            PatientVisits visit = (PatientVisits) data.get("visit");

            // 创建表格来布局基本信息
            PdfPTable table = new PdfPTable(2);
            table.setWidthPercentage(100);
            table.setSpacingBefore(10);
            table.setSpacingAfter(10);

            // 添加基本信息到表格
            addTableRow(table, "患者姓名：", visit.getName(), contentFont);
            addTableRow(table, "性别：", visit.getGender(), contentFont);
            addTableRow(table, "年龄：", visit.getAge() + "岁", contentFont);
            addTableRow(table, "就诊时间：",
                    new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(visit.getVisitTime()),
                    contentFont);
            addTableRow(table, "就诊科室：", visit.getDepartment(), contentFont);
            addTableRow(table, "主治医生：", visit.getDoctor(), contentFont);
            document.add(table);

            // 添加分隔线
            LineSeparator line = new LineSeparator();
            document.add(line);

            // 添加诊断信息
            document.add(Chunk.NEWLINE);
            Paragraph diagnosisTitle = new Paragraph("诊断信息", subtitleFont);
            diagnosisTitle.setSpacingBefore(10);
            diagnosisTitle.setSpacingAfter(10);
            document.add(diagnosisTitle);

            document.add(new Paragraph("主诉：" + visit.getMainComplaint(), contentFont));
            document.add(new Paragraph("现病史：" + visit.getPresentIllness(), contentFont));
            document.add(new Paragraph("既往史：" + visit.getPastHistory(), contentFont));
            document.add(new Paragraph("过敏史：" + visit.getAllergy(), contentFont));
            document.add(new Paragraph("诊断结果：" + visit.getDiagnosis(), contentFont));

            // 添加分隔线
            document.add(Chunk.NEWLINE);
            document.add(line);

            // 添加底部信息
            document.add(Chunk.NEWLINE);
            document.add(new Paragraph("导出时间：" + data.get("currentTime"), contentFont));
            document.add(new Paragraph("医院名称：" + data.get("hospital"), contentFont));

            document.close();
            return baos.toByteArray();

        } catch (Exception e) {
            log.error("生成PDF文档失败", e);
            throw new BusinessException("生成PDF文档失败", e);
        }
    }

    // 辅助方法：添加表格行
    private void addTableRow(PdfPTable table, String label, String value, Font font) {
        PdfPCell labelCell = new PdfPCell(new Phrase(label, font));
        labelCell.setBorder(Rectangle.NO_BORDER);
        labelCell.setPaddingBottom(5);
        table.addCell(labelCell);

        PdfPCell valueCell = new PdfPCell(new Phrase(value, font));
        valueCell.setBorder(Rectangle.NO_BORDER);
        valueCell.setPaddingBottom(5);
        table.addCell(valueCell);
    }

    /*
    * 生成Word文档的方法
    * 首先，通过 FileInputStream 读取 Word 模板文件。
        接着，利用 XWPFDocument 类加载文档。
        调用 replacePlaceholders 方法替换文档中的占位符。
        最后，将生成的文档写入 ByteArrayOutputStream 并返回字节数组。
    * */
    private byte[] generateWord(String templatePath, Map<String, Object> data) {
        try (FileInputStream fis = new FileInputStream(templatePath);
             XWPFDocument document = new XWPFDocument(fis);
             ByteArrayOutputStream baos = new ByteArrayOutputStream()) {

            // 替换文档中的占位符
            replacePlaceholders(document, data);

            // 保存文档到输出流
            document.write(baos);
            return baos.toByteArray();
        } catch (IOException e) {
            throw new BusinessException("生成 Word 文档失败", e);
        }
    }

    /*
    * 遍历文档中的所有段落和表格单元格，调用 replacePlaceholdersInParagraph 方法替换占位符。
    * */
    private void replacePlaceholders(XWPFDocument document, Map<String, Object> data) {
        // 遍历所有段落
        for (XWPFParagraph paragraph : document.getParagraphs()) {
            replacePlaceholdersInParagraph(paragraph, data);
        }

        // 遍历所有表格
        for (XWPFTable table : document.getTables()) {
            for (XWPFTableRow row : table.getRows()) {
                for (XWPFTableCell cell : row.getTableCells()) {
                    for (XWPFParagraph paragraph : cell.getParagraphs()) {
                        replacePlaceholdersInParagraph(paragraph, data);
                    }
                }
            }
        }
    }

    /*
    * 运用正则表达式匹配段落中的占位符（格式为 ${placeholder}）。
        从 data 映射中获取占位符对应的值并进行替换。
        清空原段落内容，添加替换后的文本。
    * */
    private void replacePlaceholdersInParagraph(XWPFParagraph paragraph, Map<String, Object> data) {
        String text = paragraph.getText();
        Pattern pattern = Pattern.compile("\\$\\{([^}]+)\\}");
        Matcher matcher = pattern.matcher(text);

        while (matcher.find()) {
            String placeholder = matcher.group(1);
            Object value = data.get(placeholder);
            if (value != null) {
                String replacement = value.toString();
                text = text.replace("${" + placeholder + "}", replacement);
            }
        }

        // 清空原段落内容
        while (paragraph.getRuns().size() > 0) {
            paragraph.removeRun(0);
        }
        // 添加替换后的文本
        XWPFRun run = paragraph.createRun();
        run.setText(text);
    }


    /*
    ****************************生成分享的图片*******************************
    * */
    @Override
    public String generateShareImage(Integer visitId) {
        try {
            //获取数据库中数据
            PatientVisits visitRecord = patientVisitsDao.selectByPatientId(visitId);

            // 创建图片缓存对象
            BufferedImage image = new BufferedImage(WIDTH, HEIGHT, BufferedImage.TYPE_INT_RGB);
            Graphics2D g2d = image.createGraphics();

            // 设置图片质量
            g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
            g2d.setRenderingHint(RenderingHints.KEY_TEXT_ANTIALIASING, RenderingHints.VALUE_TEXT_ANTIALIAS_ON);

            // 绘制白色背景
            g2d.setColor(Color.WHITE);
            g2d.fillRect(0, 0, WIDTH, HEIGHT);

            // 绘制顶部背景
            g2d.setColor(new Color(41, 121, 255));
            g2d.fillRect(0, 0, WIDTH, 200);


            // 创建 URL 对象
            URL url = new URL(logoPath);
            // 从 URL 读取图片
            BufferedImage logo = ImageIO.read(url);
            int PADDING = 20;
            // 在同一个 g2d 上绘制图片
            g2d.drawImage(logo, PADDING, 40, 120, 120, null);

            log.info("绘制医院logo成功---------------------------------------------------------");

            // 绘制医院名称
            g2d.setColor(Color.WHITE);
            g2d.setFont(new java.awt.Font(FONT_NAME, Font.BOLD, 32));
            g2d.drawString(hospitalName, 180, 100);

            // 绘制就诊信息
            g2d.setColor(Color.BLACK);
//            Font.FontFamily fontFamily = Font.FontFamily.valueOf(FONT_NAME);
            g2d.setFont(new java.awt.Font(FONT_NAME, Font.BOLD, 28));    //直接设置
//            g2d.setFont(new java.awt.Font(com.itextpdf.text.Font.FontFamily.valueOf(FONT_NAME).toString(), Font.BOLD, 28));
            String patientName = (String) visitRecord.getName();
            String visitTime = null;
            Date visitTimeObj = visitRecord.getVisitTime();
            if (visitTimeObj != null) {
                // 将 Date 转换为 ZonedDateTime
                ZonedDateTime zonedDateTime = visitTimeObj.toInstant().atZone(ZoneId.systemDefault());
                DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy年MM月dd日 HH:mm");
                visitTime = zonedDateTime.format(formatter);
            }
            String doctorName = (String) visitRecord.getDoctor();
            String diagnosis = (String) visitRecord.getDiagnosis();
            String treatment = (String) visitRecord.getCheckResult();

            log.info("绘制就诊信息成功------------------------------------------------");

            int y = 240;
            g2d.drawString("就诊信息", PADDING, y);

            g2d.setFont(new java.awt.Font(FONT_NAME, java.awt.Font.PLAIN, 24));
            y += 60;
            g2d.drawString("患者姓名：" + patientName, PADDING, y);
            y += 50;
            g2d.drawString("就诊时间：" + visitTime, PADDING, y);
            y += 50;
            g2d.drawString("主治医生：" + doctorName, PADDING, y);

            // 绘制诊断信息
            y += 80;
            g2d.setFont(new java.awt.Font(FONT_NAME, Font.BOLD, 28));
            g2d.drawString("诊断结果", PADDING, y);

            g2d.setFont(new java.awt.Font(FONT_NAME, java.awt.Font.PLAIN, 24));
            drawMultilineText(g2d, diagnosis, PADDING, y + 50, WIDTH - PADDING * 2);

            // 绘制医生建议
            y += 200;
            g2d.setFont(new java.awt.Font(FONT_NAME, Font.BOLD, 28));
            g2d.drawString("医生建议", PADDING, y);

            g2d.setFont(new java.awt.Font(FONT_NAME, java.awt.Font.PLAIN, 24));
            drawMultilineText(g2d, treatment, PADDING, y + 50, WIDTH - PADDING * 2);

            log.info("绘制医生建议成功-------------------------------------");

            // 生成并绘制小程序码
            String page = "pages/consultation/consultation";

//            String scene = "id=" + visitRecord.getPatientId();
            BufferedImage qrCode = wxMaUtil.generateMiniCode(page, String.valueOf(visitRecord.getPatientId()), 200);
            g2d.drawImage(qrCode, (WIDTH - 200) / 2, HEIGHT - 280, null);

            log.info("绘制小程序码成功--------------------------------------");

            // 绘制底部提示文字
            g2d.setFont(new java.awt.Font(FONT_NAME, java.awt.Font.PLAIN, 20));
            g2d.setColor(Color.GRAY);
            String tip = "微信扫一扫，查看就诊详情";
            int tipWidth = g2d.getFontMetrics().stringWidth(tip);
//            g2d.drawString(tip, (WIDTH - tipWidth) / 2, HEIGHT - 40);
            // 原来的y坐标是HEIGHT - 40 ，这里将其增大，比如增大30，可按需调整
            g2d.drawString(tip, (WIDTH - tipWidth) / 2, HEIGHT - 40 + 25);

            // 释放资源
            g2d.dispose();

            // 生成文件名
            String fileName = "share_" + visitRecord.getPatientId() + "_" + System.currentTimeMillis() + ".png";

            // 在对象名前添加 share/ 前缀
            String objectName = "share/" + fileName;

            // 将图片转换为字节数组
            ByteArrayOutputStream baos = new ByteArrayOutputStream();
            ImageIO.write(image, "PNG", baos);
            byte[] imageBytes = baos.toByteArray();

            log.info("图片转换为字节数组成功-----------------------------------");

            // 确保存储桶存在
            boolean bucketExists = minioClient.bucketExists(BucketExistsArgs.builder()
                    .bucket(shareBucket)
                    .build());
            if (!bucketExists) {
                minioClient.makeBucket(MakeBucketArgs.builder()
                        .bucket(shareBucket)
                        .build());
            }

            // 上传到MinIO
            minioClient.putObject(
                    PutObjectArgs.builder()
                            .bucket(shareBucket)
                            .object(objectName)
                            .stream(new ByteArrayInputStream(imageBytes), imageBytes.length, -1)
                            .contentType("image/png")
                            .build()
            );

            log.info("上传到Minio成功-----------------------------------------------");

            return fileName;
        } catch (Exception e) {
            throw new RuntimeException("生成分享图片失败", e);
        }
    }

    /*
    * 画图方法
    * */
    private void drawMultilineText(Graphics2D g2d, String text, int x, int y, int maxWidth) {
        FontMetrics metrics = g2d.getFontMetrics();
        String[] words = text.split("");
        StringBuilder line = new StringBuilder();
        int lineY = y;

        for (String word : words) {
            if (metrics.stringWidth(line + word) < maxWidth) {
                line.append(word);
            } else {
                g2d.drawString(line.toString(), x, lineY);
                line = new StringBuilder(word);
                lineY += metrics.getHeight() + 5;
            }
        }
        if (line.length() > 0) {
            g2d.drawString(line.toString(), x, lineY);
        }
    }

//    private static final Logger logger = LoggerFactory.getLogger(PatientVisitsService.class);

    /*
     * 生成小程序码方式1：返回base64数据
     * 生成小程序码并缓存
     */
    @Override
    public String generateAndCacheQrcode(String page, String scene, int width) {
        // 1. 先从Redis获取
        String cacheKey = QRCODE_CACHE_KEY + scene;
        String cachedQrcode = redisTemplate.opsForValue().get(cacheKey);
        if (cachedQrcode != null) {
            return cachedQrcode;
        }

        try {
            // 2. 生成新的二维码
            String base64Qrcode = wxMaUtil.generateMiniCodeBase64(page, scene, width);

            // 3. 存入Redis缓存
            redisTemplate.opsForValue().set(cacheKey, base64Qrcode, QRCODE_CACHE_TIME, TimeUnit.SECONDS);

            return base64Qrcode;
        } catch (Exception e) {
            log.error("生成小程序码失败", e);
            throw new RuntimeException("生成小程序码失败", e);
        }
    }

    /**
     * 生成小程序码方式2，返回minio URL
     * @param visitId 就诊ID
     * @param page 小程序页面路径
     * @return 上传到MinIO后的二维码URL
     */
    @Override
    public String generateQrcode(Long visitId, String page) {
        try {
            // 获取微信接口调用凭证
            String accessToken = wxAccessTokenService.getAccessToken();

            // 构建请求参数
            Map<String, Object> params = new HashMap<>();
            params.put("scene", "visitId=" + visitId);
            params.put("page", page);
            params.put("width", 280);
            params.put("auto_color", false);
            Map<String, Object> lineColor = new HashMap<>();
            lineColor.put("r", 0);
            lineColor.put("g", 0);
            lineColor.put("b", 0);
            params.put("line_color", lineColor);
            params.put("is_hyaline", false);

            // 设置请求头
            HttpHeaders headers = new HttpHeaders();
            headers.setContentType(MediaType.APPLICATION_JSON);

            // 发起请求获取小程序码
            ResponseEntity<byte[]> response = restTemplate.exchange(
                    "https://api.weixin.qq.com/wxa/getwxacodeunlimit?access_token=" + accessToken,
                    HttpMethod.POST,
                    new HttpEntity<>(params, headers),
                    byte[].class
            );

            // 检查响应
            if (response.getStatusCode() == HttpStatus.OK && response.getBody() != null) {
                byte[] qrcodeBytes = response.getBody();

                // 微信返回错误信息时也是200状态，但内容是JSON，需要检查
                if (isJsonResponse(qrcodeBytes)) {
                    log.error("微信返回错误: {}", new String(qrcodeBytes));
                    throw new RuntimeException("微信接口返回错误");
                }

                // 生成唯一文件名
                String fileName = "qrcode/" + visitId + "_" + UUID.randomUUID().toString() + ".jpg";

                // 上传到MinIO
                return minioUtil.uploadImage(qrcodeBytes, fileName);
            } else {
                log.error("获取微信小程序码失败: {}", response);
                throw new RuntimeException("获取微信小程序码失败");
            }
        } catch (Exception e) {
            log.error("生成二维码异常", e);
            throw new RuntimeException("生成二维码失败: " + e.getMessage());
        }
    }

    /**
     * 判断字节数组是否为JSON响应
     */
    private boolean isJsonResponse(byte[] data) {
        if (data.length < 2) return false;
        // JSON对象以{开头
        return data[0] == '{' && data[1] != (byte) 0xFF;
    }


}

